/*
** servidor.c este es el servidor transaccional del tp
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3700"  // the port users will be connecting to
#define PORTBANK "3701"
#define PORTCELULAR "3702"

#define BACKLOG 10	 // how many pending connections queue will hold
#define MAXDATASIZE 999



// http://www.arrakis.es/~dmrq/beej/faq.html#AEN1470
int recvtimeout(int s, char *buf, int len, int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // Construir el conjunto de descriptores de fichero
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // Construir la estructura timeval del temporizador
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // Esperar hasta que se reciban datos o venza el temporizador
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // ¡El temporizador ha vencido!
    if (n == -1) return -1; // error

    // Los datos deben estar ahí, así que llamo normalmente a recv()
    return recv(s, buf, len, 0);
}


int respuesta_personalizada(int port, char *buffer_entrada)
{
    switch (port) {
        case 3701: // banco
            if (!strcmp("OK", buffer_entrada)) // ok
                return 2;
            if (!strcmp("SIN_SALDO", buffer_entrada))
                return 3;
            if (!strcmp("CARD_FAIL", buffer_entrada))
                return 4;
        case 3702: // prestadora
            if (!strcmp("OK", buffer_entrada)) // ok
                return 2;
            if (!strcmp("NRO_INEXISTENTE", buffer_entrada))
                return 3;
    }
}



int conectate_con_el(int port, char *mensaje, int tiempo_de_espera)
{
    int sockfd, numbytes;  
    struct hostent *he;
    struct sockaddr_in their_addr; // información de la dirección de destino
    char buffer_salida[1000] = {'\0'}; // lo que se va a enviar al servidor
    char buffer_entrada[1000] = {'\0'}; // lo que se recibe


    // esto esta mal, porque deberia ser la direccion del server, mientras es localhost
    if ((he=gethostbyname("localhost")) == NULL) {  // obtener información de máquina 
        perror("gethostbyname");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    their_addr.sin_family = AF_INET;    // Ordenación de bytes de la máquina
    their_addr.sin_port = htons(port);  // short, Ordenación de bytes de la red 
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(&(their_addr.sin_zero), '\0', 8);  // poner a cero el resto de la estructura 

    if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
	perror("connect");
	return -4;
        exit(1);
    }

    
    if (send(sockfd, mensaje, strlen(mensaje), 0) == -1)
        perror("send");

    numbytes = recvtimeout(sockfd, buffer_entrada, sizeof(buffer_entrada), tiempo_de_espera);

    if (numbytes == -1) {
        // ocurrió un error
        perror("recvtimeout");
        return -1;
    }
    else if (numbytes == -2) {
        // El temporizador venció
        perror("ERROR DE COMUNICACION\n(el temporizador vencio)");
        return -2;
    }
    else if (numbytes == 0) {
        // cerro la conexion el servidor
        printf("Cerro la conexion\n");
        return -3;
    }
    else {
        // Hay datos en el buffer
        printf("Llego datos\n");
        printf("%s\n", buffer_entrada);
        close(sockfd);
        return respuesta_personalizada(port, buffer_entrada);
    }

    close(sockfd);

    return (0);
}





void sigchld_handler(int s)
{
	while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}


char *tarjeta_pin_monto(char *entrada)
{
    static char salida[34]; /* 16 numeros de tarjeta de credito,
                                \n separador,
                                4 numeros de pin,
                                monto hasta 12 digitos,
                                en total 21*/
    int i = 0, j = 0;
    
    while (entrada[i] != '\n')
        i++; // llegamos al primer '\n', es decir, saltamos el num_telefono
    i++; // ahora i es el indice del primer digito de la tarjeta
    
    // copiamos el numero de tarjeta
    while (entrada[i] != '\n') {
        salida[j] = entrada[i];
        i++;
        j++;
    }
    salida[j++] = entrada[i++]; // copiamos tambien el \n
    
    // copiamos el pin de la tarjeta
    while (entrada[i] != '\n') {
        salida[j] = entrada[i];
        i++;
        j++;
    }
    salida[j++] = entrada[i++]; // copiamos tambien el \n

    // copiamos el monto
    while (entrada[i] != '\n') {
        salida[j] = entrada[i];
        i++;
        j++;
    }
    salida[j] = '\0';
    
    return salida;
}


char *numero_cel_monto(char *entrada)
{
    static char salida[23]; /* 10 digitos, numero de celular,
                                \n separador,
                                monto hasta 12 digitos,
                                en total 22*/
    int i = 0, j = 0;
    char contador = 0;

    // copiamos el numero de celular
    while (entrada[i] != '\n') {
        salida[j] = entrada[i];
        i++;
        j++;
    }
    salida[j++] = entrada[i++]; // copiamos tambien el \n
    
    // copiamos el monto de la transaccion
    while (contador != 2) {
        switch (entrada[i]) {
            case '\n': contador++; // contamos dos veces los '\n' para llegar al monto
        }
        i++;
    }

    // copiamos el monto
    while (entrada[i] != '\n') {
        salida[j] = entrada[i];
        i++;
        j++;
    }

    salida[j] = '\0';
    
    return salida;
}


int main(void)
{
	int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
	struct addrinfo hints, *servinfo, *p;
	struct sockaddr_storage their_addr; // connector's address information
	socklen_t sin_size;
	struct sigaction sa;
	int yes=1;
	char s[INET6_ADDRSTRLEN];
    char buf[1000] = {'\0'};
	int rv;
	char *msj_para_banco, *msj_para_prestadora;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE; // use my IP

	if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}

	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype,
				p->ai_protocol)) == -1) {
			perror("server: socket");
			continue;
		}

		if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
				sizeof(int)) == -1) {
			perror("setsockopt");
			exit(1);
		}

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("server: bind");
			continue;
		}

		break;
	}

	if (p == NULL)  {
		fprintf(stderr, "server: failed to bind\n");
		return 2;
	}

	freeaddrinfo(servinfo); // all done with this structure

	if (listen(sockfd, BACKLOG) == -1) {
		perror("listen");
		exit(1);
	}

	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) {
		perror("sigaction");
		exit(1);
	}

	printf("Server: esperando conexiones...\n");

	while(1) {  // main accept() loop
		sin_size = sizeof their_addr;
		new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
		if (new_fd == -1) {
			perror("accept");
			continue;
		}

		inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof(s));
		printf("Servidor: llego conexion del %s\n", s);

		if (!fork()) { // this is the child process
			close(sockfd); // child doesn't need the listener
            if ((rv = recv(new_fd, buf, MAXDATASIZE-1, 0)) == -1) {
                perror("recv");
                exit(1);
            }
            printf("%s", buf);
            // aca tenemos que conectarnos con el banco
            printf("servidor tratando de conectarse con el banco\n");
            // aca obtenemos el numero de tarjeta, pin y monto
            msj_para_banco = tarjeta_pin_monto(buf);
            switch(conectate_con_el(3701, msj_para_banco, 15)) {
                case -1:
                    // aca hay que poner un mensaje de error
                    // mirar la  funcion "conectate con el"
                    printf("Ocurrio un error\n");
                    break;
                case -2:
                    printf("TimeOut con el Banco\n");
                    break;
                case -3:
                    printf("El banco cerro la conexion y no respondio nada\n");
                    break;
                case 3:
                    if (send(new_fd, "SIN_SALDO", strlen("SIN_SALDO"), 0) == -1)
                        perror("send en el hijo");
                    break;
                case 4:
                    if (send(new_fd, "Tarjeta Inexistente", strlen("Tarjeta Inexistente"), 0) == -1)
                        perror("send en el hijo");
                    break;
				case -4:
					 if (send(new_fd, "No se puede conectar con el banco, amontema", strlen("No se puede conectar con el banco, amontema"), 0) == -1)
                        perror("send en el hijo");
                    break;
                case 2: // este es el caso exitoso
                    // obtenemos el numero de telefono y el monto a acreditar
                    msj_para_prestadora = numero_cel_monto(buf);
                    // aqui tenemos que conectarnos con la prestadora
                    switch(conectate_con_el(3702, msj_para_prestadora, 10)) {
                        case 2:
                            if (send(new_fd, "OK", strlen("OK"), 0) == -1)
                                perror("send en el hijo");
                            /* OJO 
                            creo que aqui recien debemos otra vez conectarnos
                            al banco
                            y aqui recien deberia restar el saldo de la tarjeta
                            */
                            // bueno, segun los companheros (Akagi, por ejemplo) dicen que en la primera llamada al banco ya se le descuenta el saldo. Aqui ya es donte termina el proceso con exito.
                            break;
                        case 3:
                            if (send(new_fd, "NRO_INEXISTENTE", strlen("NRO_INEXISTENTE"), 0) == -1)
                                perror("send en el hijo");
                            // aca le enviamos el rollback al banco, ya que el # de cel. no existe
                            printf("Numero inexistente\n");
                            strcat(msj_para_banco, "\nROLLBACK");
                            if (conectate_con_el(3701, msj_para_banco, 15) == 2)
                                printf("Rollback en el banco\n");
			                else
                                printf("Error al hacer rollback con el banco\n");
                            break;

                        case -1:
                            if (send(new_fd, "Error en la funcion", strlen("Error en la funcion"), 0) == -1)
                                perror("send en el hijo");
                            // aca le enviamos el rollback al banco, ya que el # de cel. no existe
                            printf("Ocurrio un error en la funcion\n");
                            strcat(msj_para_banco, "\nROLLBACK");
                            if (conectate_con_el(3701, msj_para_banco, 15) == 2)
                                printf("Rollback en el banco\n");
			                else
                                printf("Error al hacer rollback con el banco\n");
                            break;

                        case -2:
                            if (send(new_fd, "TimeOut con la prestadora", strlen("TimeOut con la prestadora"), 0) == -1)
                                perror("send en el hijo");
                            // aca le enviamos el rollback al banco, ya que el # de cel. no existe
                            printf("TimeOut con la prestadora\n");
                            strcat(msj_para_banco, "\nROLLBACK");
                            if (conectate_con_el(3701, msj_para_banco, 15) == 2)
                                printf("Rollback en el banco\n");
			                else
                                printf("Error al hacer rollback con el banco\n");
                            break;
                        case -3:
                       		if (send(new_fd, "La prestadora cerro la conexion", strlen("La prestadora cerro la conexion"), 0) == -1)
                                perror("send en el hijo");
                            // aca le enviamos el rollback al banco, ya que el # de cel. no existe
                            printf("La prestadora cerro la conexion y no respondio nada\n");
                            strcat(msj_para_banco, "\nROLLBACK");
                            if (conectate_con_el(3701, msj_para_banco, 15) == 2)
                                printf("Rollback en el banco\n");
			                else
                                printf("Error al hacer rollback con el banco\n");
                            break;
                        
                            printf("La prestadora cerro la conexion y no respondio nada\n");
                            break;
						case -4:
							 if (send(new_fd, "No se puede conectar con la prestadora, amontema", strlen("No se puede conectar con el banco, amontema"), 0) == -1)
                        perror("send en el hijo");
							strcat(msj_para_banco, "\nROLLBACK");
                            if (conectate_con_el(3701, msj_para_banco, 15) == 2)
                                printf("Rollback en el banco\n");
			                else
                                printf("Error al hacer rollback con el banco\n");
                            break;
						break;
			
                    }
                    break;
            }
            close(new_fd);
            exit(0);
		}
		close(new_fd);  // parent doesn't need this
	}

	return 0;
}

